package org.linphone.purchase;
/*
InAppPurchaseHelper.java
Copyright (C) 2015  Belledonne Communications, Grenoble, France

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

import java.util.ArrayList;
import java.util.regex.Pattern;

import org.json.JSONException;
import org.json.JSONObject;
import org.linphone.LinphonePreferences;
import org.linphone.mediastream.Log;
import org.linphone.xmlrpc.XmlRpcHelper;
import org.linphone.xmlrpc.XmlRpcListenerBase;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Patterns;

import com.android.vending.billing.IInAppBillingService;

/**
 * @author Sylvain Berfini
 */
public class InAppPurchaseHelper {
	public static final int API_VERSION = 3;
	public static final int ACTIVITY_RESULT_CODE_PURCHASE_ITEM = 11089;
	
    public static final String SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
    public static final String SKU_DETAILS_LIST = "DETAILS_LIST";
    public static final String SKU_DETAILS_PRODUCT_ID = "productId";
    public static final String SKU_DETAILS_PRICE = "price";
    public static final String SKU_DETAILS_TITLE = "title";
    public static final String SKU_DETAILS_DESC = "description";
    
    public static final String ITEM_TYPE_INAPP = "inapp";
    public static final String ITEM_TYPE_SUBS = "subs";
    
    public static final int RESPONSE_RESULT_OK = 0;
    public static final int RESULT_USER_CANCELED = 1;
    public static final int RESULT_SERVICE_UNAVAILABLE = 2;
    public static final int RESULT_BILLING_UNAVAILABLE = 3;
    public static final int RESULT_ITEM_UNAVAILABLE = 4;
    public static final int RESULT_DEVELOPER_ERROR = 5;
    public static final int RESULT_ERROR = 6;
    public static final int RESULT_ITEM_ALREADY_OWNED = 7;
    public static final int RESULT_ITEM_NOT_OWNED = 8;
    
    public static final String RESPONSE_CODE = "RESPONSE_CODE";
    public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
    public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
    public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
    public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
    public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
    public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
    public static final String RESPONSE_INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
    
    public static final String PURCHASE_DETAILS_PRODUCT_ID = "productId";
    public static final String PURCHASE_DETAILS_ORDER_ID = "orderId";
    public static final String PURCHASE_DETAILS_AUTO_RENEWING = "autoRenewing";
    public static final String PURCHASE_DETAILS_PURCHASE_TIME = "purchaseTime";
    public static final String PURCHASE_DETAILS_PURCHASE_STATE = "purchaseState";
    public static final String PURCHASE_DETAILS_PAYLOAD = "developerPayload";
    public static final String PURCHASE_DETAILS_PURCHASE_TOKEN = "purchaseToken";
    
    public static final String CLIENT_ERROR_SUBSCRIPTION_PURCHASE_NOT_AVAILABLE = "SUBSCRIPTION_PURCHASE_NOT_AVAILABLE";
    public static final String CLIENT_ERROR_BIND_TO_BILLING_SERVICE_FAILED = "BIND_TO_BILLING_SERVICE_FAILED";
    public static final String CLIENT_ERROR_BILLING_SERVICE_UNAVAILABLE = "BILLING_SERVICE_UNAVAILABLE";
    
	private Context mContext;
	private InAppPurchaseListener mListener;
	private IInAppBillingService mService;
	private ServiceConnection mServiceConn;
	private Handler mHandler = new Handler();
	private String mGmailAccount;
	
	private String responseCodeToErrorMessage(int responseCode) {
		switch (responseCode) {
		case RESULT_USER_CANCELED:
			return "BILLING_RESPONSE_RESULT_USER_CANCELED";
		case RESULT_SERVICE_UNAVAILABLE:
			return "BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE";
		case RESULT_BILLING_UNAVAILABLE:
			return "BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE";
		case RESULT_ITEM_UNAVAILABLE:
			return "BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE";
		case RESULT_DEVELOPER_ERROR:
			return "BILLING_RESPONSE_RESULT_DEVELOPER_ERROR";
		case RESULT_ERROR:
			return "BILLING_RESPONSE_RESULT_ERROR";
		case RESULT_ITEM_ALREADY_OWNED:
			return "BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED";
		case RESULT_ITEM_NOT_OWNED:
			return "BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED";
		}
		return "UNKNOWN_RESPONSE_CODE";
	}
	
	public InAppPurchaseHelper(Activity context, InAppPurchaseListener listener) {
		mContext = context;
		mListener = listener;
		mGmailAccount = getGmailAccount();


		Log.d("[In-app purchase] creating InAppPurchaseHelper for context "+context.getLocalClassName());

		mServiceConn = new ServiceConnection() {
		   @Override
		   public void onServiceDisconnected(ComponentName name) {
			   Log.d("[In-app purchase] onServiceDisconnected!");
		       mService = null;
		   }

		   @Override
		   public void onServiceConnected(ComponentName name, IBinder service) {
			   Log.d("[In-app purchase] onServiceConnected!");
			   mService = IInAppBillingService.Stub.asInterface(service);
		       String packageName = mContext.getPackageName();
		       try {
		    	   int response = mService.isBillingSupported(API_VERSION, packageName, ITEM_TYPE_SUBS);
		    	   if (response != RESPONSE_RESULT_OK || mGmailAccount == null) {
		    		   Log.e("[In-app purchase] Error: Subscriptions aren't supported!");
		    		   mListener.onError(CLIENT_ERROR_SUBSCRIPTION_PURCHASE_NOT_AVAILABLE);
		    	   } else {
				       mListener.onServiceAvailableForQueries();
		    	   }
		       } catch (RemoteException e) {
		    	   Log.e(e);
		       }
		   }
		};

		Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
		serviceIntent.setPackage("com.android.vending");
        if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
            boolean ok = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
            if (!ok) {
            	Log.e("[In-app purchase] Error: Bind service failed");
    		   mListener.onError(CLIENT_ERROR_BIND_TO_BILLING_SERVICE_FAILED);
            }
        } else {
        	Log.e("[In-app purchase] Error: Billing service unavailable on device.");
    		mListener.onError(CLIENT_ERROR_BILLING_SERVICE_UNAVAILABLE);
        }
	}
	
	private ArrayList<Purchasable> getAvailableItemsForPurchase() {
		ArrayList<Purchasable> products = new ArrayList<Purchasable>();
		ArrayList<String> skuList = LinphonePreferences.instance().getInAppPurchasables();
		Bundle querySkus = new Bundle();
		querySkus.putStringArrayList(SKU_DETAILS_ITEM_LIST, skuList);
		
		Bundle skuDetails = null;
		try {
			skuDetails = mService.getSkuDetails(API_VERSION, mContext.getPackageName(), ITEM_TYPE_SUBS, querySkus);
		} catch (RemoteException e) {
			Log.e(e);
		}

		if (skuDetails != null) {
			int response = skuDetails.getInt(RESPONSE_CODE);
			if (response == RESPONSE_RESULT_OK) {
				ArrayList<String> responseList = skuDetails.getStringArrayList(SKU_DETAILS_LIST);
				for (String thisResponse : responseList) {
					try {
						JSONObject object = new JSONObject(thisResponse);
						String id = object.getString(SKU_DETAILS_PRODUCT_ID);
						String price = object.getString(SKU_DETAILS_PRICE);
						String title = object.getString(SKU_DETAILS_TITLE);
						String desc = object.getString(SKU_DETAILS_DESC);
						
						Purchasable purchasable = new Purchasable(id).setTitle(title).setDescription(desc).setPrice(price);
						Log.w("Purchasable item " + purchasable.getDescription());
						products.add(purchasable);
					} catch (JSONException e) {
						Log.e(e);
					}
				}
			} else {
				Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response));
	    		mListener.onError(responseCodeToErrorMessage(response));
			}
		}
		
		return products;
	}
	
	public void getAvailableItemsForPurchaseAsync() {
		new Thread(new Runnable() {
            public void run() {
            	final ArrayList<Purchasable> items = getAvailableItemsForPurchase();
            	if (mHandler != null && mListener != null) {
            		mHandler.post(new Runnable() {
                        public void run() {
                        	mListener.onAvailableItemsForPurchaseQueryFinished(items);
                        }
                    });
            	}
            }
		}).start();
	}
	
	public void getPurchasedItemsAsync() {
		new Thread(new Runnable() {
            public void run() {
            	
            	final ArrayList<Purchasable> items = new ArrayList<Purchasable>();
            	String continuationToken = null;
        		do {
        			Bundle purchasedItems = null;
        			try {
        				purchasedItems = mService.getPurchases(API_VERSION, mContext.getPackageName(), ITEM_TYPE_SUBS, continuationToken);
        			} catch (RemoteException e) {
        				Log.e(e);
        			}
        			
        			if (purchasedItems != null) {
        				int response = purchasedItems.getInt(RESPONSE_CODE);
        				if (response == RESPONSE_RESULT_OK) {
        					ArrayList<String>  purchaseDataList = purchasedItems.getStringArrayList(RESPONSE_INAPP_PURCHASE_DATA_LIST);
        					ArrayList<String>  signatureList = purchasedItems.getStringArrayList(RESPONSE_INAPP_SIGNATURE_LIST);
        					continuationToken = purchasedItems.getString(RESPONSE_INAPP_CONTINUATION_TOKEN);
        					
				   			for (int i = 0; i < purchaseDataList.size(); ++i) {
				   				String purchaseData = purchaseDataList.get(i);
    				   			String signature = signatureList.get(i);
    							Log.d("[In-app purchase] " + purchaseData);
        				      
    				   			Purchasable item = verifySignature(purchaseData, signature);
    				   			if (item != null) {
    				   				items.add(item);
    				   			}
    				   		}
        				} else {
        					Log.e("[In-app purchase] Error: responde code is not ok: " + responseCodeToErrorMessage(response));
        		    		mListener.onError(responseCodeToErrorMessage(response));
        				}
        			}
        		} while (continuationToken != null);
            	
            	if (mHandler != null && mListener != null) {
            		mHandler.post(new Runnable() {
                        public void run() {
                        	mListener.onPurchasedItemsQueryFinished(items);
                        }
                    });
            	}
            }
		}).start();
	}
	
	public void parseAndVerifyPurchaseItemResultAsync(int requestCode, int resultCode, Intent data) {
		if (requestCode == ACTIVITY_RESULT_CODE_PURCHASE_ITEM) {
			int responseCode = data.getIntExtra(RESPONSE_CODE, 0);

			if (resultCode == Activity.RESULT_OK && responseCode == RESPONSE_RESULT_OK) {
				String payload = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
				String signature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);

				Purchasable item = LinphonePreferences.instance().getInAppPurchasedItem();
				item.setPayloadAndSignature(payload, signature);
				LinphonePreferences.instance().setInAppPurchasedItem(item);
				
				XmlRpcHelper xmlRpcHelper = new XmlRpcHelper();
				xmlRpcHelper.verifySignatureAsync(new XmlRpcListenerBase() {
					@Override
					public void onSignatureVerified(boolean success) {
						mListener.onPurchasedItemConfirmationQueryFinished(success);
					}
				}, payload, signature);
			}
		}
	}
	
	private void purchaseItem(String productId, String sipIdentity) {
		Bundle buyIntentBundle = null;
		try {
			buyIntentBundle = mService.getBuyIntent(API_VERSION, mContext.getPackageName(), productId, ITEM_TYPE_SUBS, sipIdentity);
		} catch (RemoteException e) {
			Log.e(e);
		}
		
		if (buyIntentBundle != null) {
			PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
			if (pendingIntent != null) {
				try {
					((Activity) mContext).startIntentSenderForResult(pendingIntent.getIntentSender(), ACTIVITY_RESULT_CODE_PURCHASE_ITEM, new Intent(), 0, 0, 0);
				} catch (SendIntentException e) {
					Log.e(e);
				}
			}
		}
	}
	
	public void purchaseItemAsync(final String productId, final String sipIdentity) {
		new Thread(new Runnable() {
            public void run() {
            	purchaseItem(productId, sipIdentity);
            }
		}).start();
	}
	
	public void destroy() {
		mContext.unbindService(mServiceConn);
	}
	
	public String getGmailAccount() {
		Account[] accounts = AccountManager.get(mContext).getAccountsByType("com.google");
		
	    for (Account account: accounts) {
	    	if (isEmailCorrect(account.name)) {
	            String possibleEmail = account.name;
	            return possibleEmail;
	        }
	    }
	    
	    return null;
	}
	
	private boolean isEmailCorrect(String email) {
    	Pattern emailPattern = Patterns.EMAIL_ADDRESS;
    	return emailPattern.matcher(email).matches();
	}
	
	private Purchasable verifySignature(String payload, String signature) {
		// TODO FIXME rework to be async
		/*XmlRpcHelper helper = new XmlRpcHelper();
		if (helper.verifySignature(payload, signature)) {
			try {
				JSONObject json = new JSONObject(payload);
				String productId = json.getString(PURCHASE_DETAILS_PRODUCT_ID);
				Purchasable item = new Purchasable(productId); 
				item.setPayloadAndSignature(payload, signature);
				return item;
			} catch (JSONException e) {
				Log.e(e);
			}
		}*/
		return null;
	}
	
	interface VerifiedSignatureListener {
		void onParsedAndVerifiedSignatureQueryFinished(Purchasable item);
	}
}
